using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using SharedObjects;
using TheGame.Server;
using TheGameLib;

namespace TheGame.Webservices
{
	public partial class TheGameService
	{
		public Guid? Connect_ConnectToSystem(Guid sessionId, List<ulong> connectionPathIds)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return null;
			}

			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			//get security system IDs from last hop (must be done here, since no DB inside ASO)
			ComputerSystem c = (from ComputerSystem cs in Db where cs.Id == connectionPathIds.Last() select cs).FirstOrDefault();
			if (c == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Target system id invalid.");
				return null;
			}
			List<SecuritySystem> securitySystems = (from ss in c.SecuritySystem select ss).ToList();
			List<ulong> securitySystemIds = (from i in securitySystems select i.Id).ToList();
			AgentConnection conn;
			Guid? connectionId;
			lock (aso)
			{
				if (!aso.Connect(connectionPathIds, securitySystemIds, out connectionId))
				{
					SendServerError(ServerErrorSeverity.Error, "Connection to target failed.");
					return null;
				}
				if (!connectionId.HasValue)
				{
					SendServerError(ServerErrorSeverity.Error, "Connection to target failed.");
					return null;
				}

				conn = aso.GetAgentConnection(connectionId.Value);
				conn.ChatNick = me.DisplayName;
				conn.TraceUpdated += ConnectionTraceUpdated;
				conn.TraceEnded += ConnectionTraceEnded;
				conn.ChatMessageReceived += ConnectionChatMessageReceived;
			}
			lock (conn)
			{
				new Thread(new ThreadStart(delegate
				{
					foreach (ulong target in connectionPathIds)
					{
						ulong targetId = target;
						ComputerSystem targetCs = (from ComputerSystem cs in Db where cs.Id == targetId select cs).FirstOrDefault();
						if (targetCs == null)
						{
							conn.Disconnect();
							SendServerError(ServerErrorSeverity.Error, "Connection to system {0} failed. Invalid hop(s) detected. Connection was killed.", conn.TargetSystemId);
							return;
						}
						//if the node is the first connection point, we log the real gateway ip)
						if (target == connectionPathIds.First() && connectionPathIds.Count > 1)
						{
							targetCs.Log(Db, me.Gateway.ComputerSystem.Id, LoggingAction.Routed, null);
							//continue;
						}
						//if the node is not the real target (and implicitly not the first host either), we log the connection routing event
						else if (target != connectionPathIds.Last())
						{
							targetCs.Log(Db, conn.GetConnectedHostId(target), LoggingAction.Routed, null);
							//continue;
						}
						else
							targetCs.Log(Db, conn.GetConnectedHostId(conn.TargetSystemId), LoggingAction.Connect, null);
					}
					Db.Commit();
				})).Start();
			}

			//if we reach here, we're at the last node, aka target system
			return connectionId;
		}//next step: Connect_GetConnectedHostFeatures: to obtain list of security systems or features

		public List<ComputerSystemFeature> Connect_GetConnectedHostFeatures(Guid sessionId, Guid connectionId)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return null;
			}
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			AgentConnection conn;
			lock (aso)
			{
				conn = aso.GetAgentConnection(connectionId);
				if (conn == null)
				{
					SendServerError(ServerErrorSeverity.Error, "Cannot fetch list of features, no connection to target system exists.");
					return null;
				}
			}
			lock (conn)
			{
				ComputerSystem c = (from ComputerSystem cs in Db where cs.Id == conn.TargetSystemId select cs).FirstOrDefault();
				if (c == null)
				{
					SendServerError(ServerErrorSeverity.Error, "Target system does not exist.");
					return null;
				}
				//List<ComputerSystemFeature> features = new List<ComputerSystemFeature>();
				//features.AddRange(c.SecuritySystem.Cast<ComputerSystemFeature>());
				//features.AddRange(c.ComputerSystemFeatures);
				//return features;

				switch (conn.CurrentConnectionState)
				{
					case SystemConnectionState.Connected:
						return c.SecuritySystem.Cast<ComputerSystemFeature>().ToList();
					case SystemConnectionState.LoggedIn:
					case SystemConnectionState.Bypassed:
						if (conn.IsHacked)
							return c.ComputerSystemFeatures.ToList();
						return c.ComputerSystemFeatures.Where(it => !it.HackedOnly).ToList(); //next step: use all provided system features
					default:
						SendServerError(ServerErrorSeverity.Error, "Invalid connection state.");
						return null;
				}
			}
		} //next step: Connect_TryAccess: bypass all returned security systems OR use provided system features

		public bool Connect_TryAccess(Guid sessionId, Guid connectionId, ulong securitySystemId, object[] methodParameters)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			AgentConnection conn;
			ulong targetSystemId;
			lock (aso)
			{
				conn = aso.GetAgentConnection(connectionId);
				targetSystemId = conn.TargetSystemId;
			}

			SecuritySystem secSys = (from SecuritySystem s in Db where s.Id == securitySystemId select s).FirstOrDefault();
			if (secSys == null)
			{
				SendServerError(ServerErrorSeverity.Error, "Security system does not exist. Cannot access.");
				return false;
			}
			//check if this secSys is assigned to our target host
			SecuritySystem sec = (
				from ComputerSystem cs in Db
				where cs.Id == targetSystemId &&
					cs.SecuritySystem != null &&
					cs.SecuritySystem.Count > 0
				from SecuritySystem ss in cs.SecuritySystem
				where ss.Id == secSys.Id
				select ss
				).FirstOrDefault();
			if (sec == null)
				return false;
			IObjectContainer localDb = Db;
			ComputerSystemAccount accountId = secSys.Access(ref localDb, targetSystemId, methodParameters);
			if (accountId == null)
			{
				//Todo: #### Modify this to be conditional. Wrong username/Typo should not set off a trace just like that
				//set off trace
				//lock(conn)
				//{
				//    conn.Trace();
				//}
				SendServerError(ServerErrorSeverity.Warning, "Access to security system denied.");
				return false;
			}

			lock (conn)
			{
				if (conn.ComputerSystemAccountId == 0)
					conn.ComputerSystemAccountId = accountId.Id;
				else if (conn.ComputerSystemAccountId != accountId.Id)
				{
					//Todo: #### Modify this to be conditional. Wrong username/Typo should not set off a trace just like that
					//conn.Trace();
					SendServerError(ServerErrorSeverity.Warning, "Used credentials don't match.");
					return false;
				}
				conn.SetAccessSuccessfull(securitySystemId, accountId.IsTemporary); //this never sets off a trace
			}
			return true;
		} //next step: Connect_LoginToSystemAllowed: ask if login to system is possible (i.e. "are all secsys bypassed")

		public bool Connect_LoginToSystemAllowed(Guid sessionId, Guid connectionId)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			AgentConnection conn;
			lock (aso)
			{
				conn = aso.GetAgentConnection(connectionId);
			}
			lock (conn)
			{
				switch (conn.CurrentConnectionState)
				{
					case SystemConnectionState.Bypassed:
						return true;
					default:
						return false;
				}
			}
		} //next step: if true: Connect_LoginToSystem to mark connection as "logged in". if false: Connect_TryAccess: keep bypassing

		public bool Connect_LoginToSystem(Guid sessionId, Guid connectionId)
		{
			if (!CheckIsValidAgent(sessionId))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			AgentConnection conn;
			lock (aso)
			{
				conn = aso.GetAgentConnection(connectionId);
				if (conn == null)
				{
					SendServerError(ServerErrorSeverity.Error, "Connection does not exist.");
					return false;
				}
			}
			lock (conn)
			{
				conn.Login();
				ComputerSystem cs = (from ComputerSystem c in Db where c.Id == conn.TargetSystemId select c).FirstOrDefault();
				if (cs == null)
					return false;
				cs.Log(Db, conn.GetConnectedHostId(conn.TargetSystemId), LoggingAction.Login, null);
				if (conn.IsHacked)
					conn.Trace(Db);
				return true;
			}
		} //next step: Connect_GetConnectedHostFeatures to obtain list of ComputerSystemFeatures - Connection successfully established

		public bool Connect_DisconnectFromSystem(Guid sessionId, Guid connectionId)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			AgentSessionObject aso = World.Instance.GetAgentSession(sessionId);
			if (aso == null)
			{
				SendServerError(ServerErrorSeverity.Fatal, "No session object found for session id.");
				return false;
			}
			AgentConnection conn;
			lock (aso)
			{
				conn = aso.GetAgentConnection(connectionId);
				if (conn == null)
				{
					SendServerError(ServerErrorSeverity.Error, "Specified connection does not exist.");
					return false;
				}
				conn.Disconnect();
				new Thread(new ThreadStart(delegate
				{
					foreach (var nodeId in conn.ConnectedNodes)
					{
						ulong id = nodeId;
						ComputerSystem cs = (from ComputerSystem c in Db where c.Id == id select c).FirstOrDefault();
						if (cs == null)
							continue;
						if (nodeId == conn.ConnectedNodes.First())
						{
							cs.Log(Db, me.Gateway.ComputerSystem.Id, LoggingAction.Disconnect, "");
							continue;
						}
						cs.Log(Db, conn.GetConnectedHostId(nodeId), LoggingAction.Disconnect, "");
					}
					Db.Commit();
				})).Start();
				aso.Connections.Remove(conn);
			}
			return true;
		}

		public List<SavedConnection> Connect_GetConnections(Guid sessionId)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return null;
			}
			return me.SavedConnections.Where(it => !it.IsDeleted).ToList();
		}

		public bool Connect_SaveConnection(Guid sessionId, SavedConnection conn)
		{
			Agent me;
			if (!CheckIsValidAgent(sessionId, out me))
			{
				SendServerError(ServerErrorSeverity.Error, "Invalid session id.");
				return false;
			}
			SavedConnection sc = (from x in me.SavedConnections where x.Id == conn.Id select x).FirstOrDefault();
			if (sc == null)
			{
				sc = new SavedConnection { Id = Guid.NewGuid().ToLong(), Name = conn.Name, Route = conn.Route };
				me.SavedConnections.Add(sc);
				Db.PersistEntity(me);
			}
			else
			{
				if (conn.Route.Count == 0)
				{
					sc.IsDeleted = true;
					sc.DeletionTimeStamp = DateTime.Now;
				}
				else
				{
					sc.Route.Clear();
					sc.Route.AddRange(conn.Route);
				}
				Db.PersistEntity(sc);
			}
			Db.Commit();
			return true;
		}
	}
}